////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Daniel F. Hanson
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace SilverLua.Runtime
{
   /// <summary>
   /// An execution model for SilverLua Chunks.
   /// </summary>
   public class Script
   {
      private readonly int MaxCallStackSize;
      
      private Environment _environment;
      
      internal Chunk[] Chunks { get; private set; }
      
      private Chunk[] _callStack;
      private int _callStackIndex = 0;
      private int[] _instructionIndex;
      
      private Stopwatch _stopwatch = new Stopwatch();
      private static readonly TimeSpan TimesliceDuration = TimeSpan.FromMilliseconds(3);
      
      public ScriptState State { get; private set; }
      
      internal List<Value[]> RegisterStack { get; private set; }
      internal List<Value[]> UpvalueStack { get; private set; }
      internal List<Value> ArgumentStack { get; private set; }
      
      private Value[] _returnArray;
      
      private const int MaxReturnCount = 64;
      
      internal Script(Environment env, Chunk[] chunks, int maxCallStackSize)
      {
         _environment = env;
         Chunks = chunks;
         MaxCallStackSize = maxCallStackSize;
         _callStack = new Chunk[MaxCallStackSize];
         _callStack[0] = chunks[0];
         _instructionIndex = new int[MaxCallStackSize];
         _instructionIndex[0] = 0;
         State = ScriptState.Ready;
         RegisterStack = new List<Value[]>();
         UpvalueStack = new List<Value[]>();
         ArgumentStack = new List<Value>();
         
         _returnArray = new Value[MaxReturnCount];
         for (int i = 0; i < MaxReturnCount; ++i)
         {
            _returnArray[i] = new Value();
         }

         RegisterStack.Add(new Value[chunks[0].RegisterCount]);
         for (int i = 0; i < RegisterStack[0].Length; ++i)
         {
            RegisterStack[0][i] = new Value();
         }
         
         // Main chunk never has upvalues
         UpvalueStack.Add(new Value[0]);
      }

      internal Value GetRegister(int index)
      {
         return RegisterStack[RegisterStack.Count - 1][index];
      }

      internal Value GetParameter(int index)
      {
         return ArgumentStack[ArgumentStack.Count - index - 1];
      }
      
      internal Value GetUpvalue(int index)
      {
         return UpvalueStack[UpvalueStack.Count - 1][index];
      }
      
      internal Value GetReturnValue(int index)
      {
         return _returnArray[index];
      }
      
      internal bool ReturnValue(object value, Type returnType)
      {
         if (returnType == typeof(int))
         {
            _returnArray[0].SetValue((int)value);
         }
         else if (returnType == typeof(float))
         {
            _returnArray[0].SetValue((float)value);
         }
         else if (returnType == typeof(string))
         {
            _returnArray[0].SetValue((string)value);
         }
         else if (returnType == typeof(bool))
         {
            _returnArray[0].SetValue((bool)value);
         }
         else
         {
            return false;
         }
         
         return true;
      }
      
      internal void ReturnHostObject(object obj, Table prototype)
      {
         _returnArray[0].SetValue(obj, prototype);
      }
      
      internal void ReturnNil()
      {
         _returnArray[0].SetToNil();
      }
      
      internal string GetString(int index)
      {
         return _callStack[_callStackIndex].Strings[index];
      }
      
      internal void PushChunk(Script script, int chunkIndex, Value[] args, Value[] upvalues)
      {
         if (_callStackIndex >= _callStack.Length - 1)
         {
            throw new Exception(string.Format("Call stack overflow (current limit is {0})", MaxCallStackSize));
         }
         
         Chunk chunk = script.Chunks[chunkIndex];
         
         _callStack[++_callStackIndex] = chunk;
         _instructionIndex[_callStackIndex] = 0;
         
         RegisterStack.Add(new Value[chunk.RegisterCount]);
         for (int i = 0; i < chunk.RegisterCount; ++i)
         {
            RegisterStack[RegisterStack.Count - 1][i] = new Value();
         }
         
         ArgumentStack.Add(null);
         for (int i = args.Length - 1; i >= 0; --i)
         {
            ArgumentStack.Add(args[i]);
         }
         
         UpvalueStack.Add(upvalues);
      }
      
      internal void PopChunk(int retBegin, int retCount)
      {
         if (_callStackIndex == 0)
         {
            Stop();
            return;
         }
         else if (_callStackIndex < 0)
         {
            throw new Exception("Call stack underflow");
         }
         
         for (int i = 0; i < retCount; ++i)
         {
            _returnArray[i].SetValue(RegisterStack[RegisterStack.Count - 1][retBegin+i]);
         }
         for (int i = retCount; i < MaxReturnCount; ++i)
         {
            _returnArray[i].SetToNil();
         }
         
         _callStack[_callStackIndex] = null;
         
         --_callStackIndex;
         
         RegisterStack.RemoveAt(RegisterStack.Count - 1);
         
         while (ArgumentStack[ArgumentStack.Count-1] != null)
         {
            ArgumentStack.RemoveAt(ArgumentStack.Count - 1);
         }
         ArgumentStack.RemoveAt(ArgumentStack.Count - 1);
         
         UpvalueStack.RemoveAt(UpvalueStack.Count - 1);
      }
      
      public void Start()
      {
         if (State == ScriptState.Ready || State == ScriptState.Done)
         {
            _environment.RunScript(this);
            State = ScriptState.Running;
         }
      }
      
      public void Stop()
      {
         State = ScriptState.Done;
      }
      
      public void Pause()
      {
         if (State == ScriptState.Running) State = ScriptState.Paused;
      }
      
      public void Resume()
      {
         if (State == ScriptState.Paused) State = ScriptState.Running;
      }
      
      internal void Jump(int offset)
      {
         _instructionIndex[_callStackIndex] += offset;
      }
      
      internal void DoTimeslice()
      {
         _stopwatch.Reset();
         _stopwatch.Start();
         
         while (_stopwatch.Elapsed < TimesliceDuration && State == ScriptState.Running)
         {
            Chunk currentChunk = _callStack[_callStackIndex];
            Instruction instruction = currentChunk.Instructions[_instructionIndex[_callStackIndex]++];
            
            _environment.VirtualMachine.Execute(instruction, this, _environment);
            
            if (instruction.Opcode != Opcode.Return &&
                _instructionIndex[_callStackIndex] >= currentChunk.Instructions.Length)
            {
               throw new Exception("End of chunk reached without return statement");
            }
         }
         
         _stopwatch.Stop();
      }
   }
   
   public enum ScriptState
   {
      Ready,
      Running,
      Paused,
      Done
   }
}
